/*
 * Created on Dec 31, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.scohen.juploadr.app;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.scohen.juploadr.Messages;
import org.scohen.juploadr.app.geo.GeoTab;
import org.scohen.juploadr.app.tags.TagParser;
import org.scohen.juploadr.app.tags.TagsHelper;
import org.scohen.juploadr.prefs.Configuration;
import org.scohen.juploadr.ui.ReusableUIFactory;
import org.scohen.juploadr.ui.Transforms;
import org.scohen.juploadr.ui.thumbnail.Thumbnail;
import org.scohen.juploadr.uploadapi.ImageUploadApi;
import org.scohen.juploadr.uploadapi.abilities.Abilities;
import org.scohen.juploadr.uploadapi.abilities.OrganizeAbilities;
import org.scohen.juploadr.uploadapi.abilities.PhotoAbilities;
import org.scohen.juploadr.util.ConversionUtils;
import org.scohen.juploadr.util.TextUtils;

import au.com.lastweekend.jim.util.StringUtils;

import com.drew.metadata.MetadataException;
import com.drew.metadata.Tag;

/**
 * @author steve
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class ImageAttributesUI implements SelectionListener {

    protected Shell window;
    protected StyledText txtTags;
    protected Text txtTitle;
    protected Text txtDescription;
    private Button btnPrevious;
    private Button btnNext;

    private Button btnOK;
    private Button btnCancel;
    private Image thumbnail;
    private boolean cancelled = false;
    private ImageAttributes attrs;

    protected Button radioPrivate;
    protected Button radioPublic;
    protected Button checkFriends;
    protected Button checkFamily;
    protected Button rotateLeft;
    protected Button rotateRight;
    protected Composite rotateButtons;
    protected Composite thumbnailComposite;
    protected GeoTab geoTab;
    private int originalRotation;

    private ReusableUIFactory uiFactory = ReusableUIFactory.getInstance();
    private Abilities abilities;
    private TagsHelper tagHelper;
    private EscListener escListener;

    protected boolean isPreviewing = false;
    private Composite meta;
    private Label lblFilename;
    private Label lblFilesize;
    private Label lblSize;
    private List<Button> photoSetButtons;

    public ImageAttributesUI(ImageAttributes attrs) {
        setAttributes(attrs);
        abilities = ImageUploadApi.getInstance().getAbilities();
    }

    /**
     * @param attrs
     */
    private void setAttributes(ImageAttributes attrs) {
        this.thumbnail = attrs.getThumbnail();
        this.attrs = attrs;
        originalRotation = attrs.getRotation();
    }

    public void show(Display parent) {
        initUI(parent);
    }

    protected void initUI(Display disp) {
        window = new Shell(disp);
        window.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                window.getDisplay().removeFilter(SWT.KeyDown, escListener);
            }
        });
        window.setText(Messages.getString("juploadr.ui.imageattributes.title") + attrs.getTitle()); //$NON-NLS-1$

        if (!Configuration.isMac()) {
            window.setImage(ReusableUIFactory.getInstance().getTinyLogoImage());
        }

        window.setLayout(new FormLayout());
        Composite buttons = new Composite(window, SWT.NONE);
        buttons.setLayout(new GridLayout());

        Composite mainForm = new Composite(window, SWT.NONE);

        mainForm.setLayout(new GridLayout(2, false));
        addImageMetadata(mainForm);
        addEditTabs(mainForm);

        FormData fd = new FormData();
        fd.top = new FormAttachment(mainForm);
        fd.bottom = new FormAttachment(100, 0);
        fd.left = new FormAttachment(0, 0);
        fd.right = new FormAttachment(100, 0);

        Composite bc = new Composite(buttons, SWT.NONE);
        bc.setLayout(new FillLayout(SWT.HORIZONTAL));
        bc.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
        addNavigationButtons(bc);

        btnOK = new Button(bc, SWT.PUSH);
        btnOK.setText(Messages.getString("juploadr.ui.button.ok")); //$NON-NLS-1$
        btnOK.addSelectionListener(this);
        btnOK.setSelection(true);
        btnOK.setSize(btnOK.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        window.setDefaultButton(btnOK);

        btnCancel = new Button(bc, SWT.PUSH);
        btnCancel.setText(Messages.getString("juploadr.ui.button.cancel")); //$NON-NLS-1$
        btnCancel.addSelectionListener(this);
        btnCancel.setSize(btnCancel.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        buttons.setLayoutData(fd);

        fd = new FormData();
        fd.bottom = new FormAttachment(100, (0 - buttons.computeSize(SWT.DEFAULT, SWT.DEFAULT).y));
        fd.top = new FormAttachment(0, 0);
        fd.left = new FormAttachment(0, 0);
        fd.right = new FormAttachment(100, 0);
        mainForm.setLayoutData(fd);

        escListener = new EscListener();
        disp.addFilter(SWT.KeyDown, escListener);
        initDefaultValues(abilities.getPhotoAbilities());

        window.pack();
        window.open();

        window.redraw();
        while (!window.isDisposed()) {
            if (!disp.readAndDispatch()) {
                disp.sleep();
            }
        }
        attrs.getButton().redraw();
    }

    /**
     * @param bc
     */
    protected void addNavigationButtons(Composite bc) {
        btnPrevious = new Button(bc, SWT.PUSH);
        btnPrevious.setText("Previous");
        btnPrevious.addSelectionListener(this);
        btnPrevious.setSelection(false);
        btnPrevious.setEnabled(attrs.hasPrevious());

        btnNext = new Button(bc, SWT.PUSH);
        btnNext.setText("Next");
        btnNext.addSelectionListener(this);
        btnNext.setSelection(false);
        btnNext.setEnabled(attrs.hasNext());
    }

    private void addEditTabs(Composite mainForm) {
        TabFolder tabs = new TabFolder(mainForm, SWT.TOP);
        tabs.setLayoutData(new GridData(GridData.FILL_BOTH));
        if (!abilities.getPhotoAbilities().allDisabled() || abilities.getRenderAbilities().isAbleToRotate()) {
            TabItem tab = new TabItem(tabs, SWT.NONE);
            tab.setControl(addMetadataTab(tabs));
            tab.setText(Messages.getString("juploadr.ui.label.photoinfo"));
        }

        if (abilities.getGeoAbilities().hasGeoAbilities()) {
            TabItem tab = new TabItem(tabs, SWT.NONE);
            geoTab = new GeoTab(tabs, attrs);
            tab.setControl(geoTab);
            tab.setText(Messages.getString("juploadr.ui.label.geo"));
        }

    }

    private Composite addMetadataTab(Composite mainForm) {
        Composite metadataFields = new Composite(mainForm, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 2;
        gridLayout.horizontalSpacing = 15;
        metadataFields.setLayout(gridLayout);
        PhotoAbilities photoAbilities = abilities.getPhotoAbilities();
        if (photoAbilities.hasPrivacy()) {

            Label l = getLabel(metadataFields, Messages.getString("juploadr.ui.label.privacy"), true); //$NON-NLS-1$
            l.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false, 2, 1));

            radioPublic = new Button(metadataFields, SWT.RADIO);
            radioPublic.setText(Messages.getString("juploadr.ui.label.public")); //$NON-NLS-1$
            radioPublic.addSelectionListener(this);
            radioPublic.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false, 1, 1));

            radioPrivate = new Button(metadataFields, SWT.RADIO);
            radioPrivate.setText(Messages.getString("juploadr.ui.label.private")); //$NON-NLS-1$
            radioPrivate.addSelectionListener(this);
            radioPrivate.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false, 1, 1));

            getCheckBoxes(metadataFields, photoAbilities);
        }
        GridData data;
        if (photoAbilities.hasTitle()) {
            getLabel(metadataFields, Messages.getString("juploadr.ui.label.title"), true); //$NON-NLS-1$
            txtTitle = getText(metadataFields, attrs.getTitle());
        }
        if (photoAbilities.hasDescription()) {
            getLabel(metadataFields, Messages.getString("juploadr.ui.label.description"), true); //$NON-NLS-1$
            txtDescription = getText(metadataFields, attrs.getDescription(), SWT.MULTI | SWT.WRAP | SWT.BORDER
                    | SWT.V_SCROLL);
            data = new GridData(GridData.FILL_BOTH);
            data.heightHint = 100;
            data.widthHint = 200;
            txtDescription.setLayoutData(data);
            txtDescription.addTraverseListener(new NoTabs());
        }
        if (photoAbilities.hasTags()) {
            getLabel(metadataFields, ""); //$NON-NLS-1$
            getLabel(metadataFields, Messages.getString("juploadr.ui.tooltip.tags")); //$NON-NLS-1$
            getLabel(metadataFields, Messages.getString("juploadr.ui.label.tags"), true); //$NON-NLS-1$            
            txtTags = new StyledText(metadataFields, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
            txtTags.setText(TagParser.toInputString(attrs.getTags()));
            txtTags.setToolTipText(Messages.getString("juploadr.ui.tooltip.tags"));
            data = new GridData(GridData.FILL_BOTH);
            data.heightHint = 50;
            data.widthHint = 200;
            txtTags.setLayoutData(data);
            tagHelper = new TagsHelper();
            txtTags.addVerifyKeyListener(tagHelper);
            txtTags.addTraverseListener(new NoTabs());

        }
        addPhotoSets(metadataFields);

        data = new GridData();
        data.horizontalAlignment = SWT.FILL;
        data.verticalAlignment = SWT.TOP;
        metadataFields.setLayoutData(data);

        return metadataFields;
    }

    private void resetFieldData() {
        PhotoAbilities photoAbilities = abilities.getPhotoAbilities();
        if (photoAbilities.hasPrivacy()) {
            radioPublic.setSelection(attrs.isPubliclyVisible());
            radioPrivate.setSelection(!attrs.isPubliclyVisible());

            if (photoAbilities.hasFriendsPrivacy()) {
                checkFriends.setSelection(attrs.isFriendViewable());
            }
            if (photoAbilities.hasFamilyPrivacy()) {
                checkFamily.setSelection(attrs.isFamilyViewable());
            }
        }
        if (photoAbilities.hasTitle()) {
            txtTitle.setText(TextUtils.getNonNullString(attrs.getTitle()));
        }

        if (photoAbilities.hasDescription()) {
            txtDescription.setText(TextUtils.getNonNullString(attrs.getDescription()));
        }

        if (photoAbilities.hasTags()) {
            txtTags.setText(TagParser.toInputString(attrs.getTags()));
            txtTags.setToolTipText(Messages.getString("juploadr.ui.tooltip.tags"));
        }
        if (abilities.getOrganizeAbilities().hasPhotoSets()) {

            for (Button b : photoSetButtons) {
                PhotoSet set = (PhotoSet) b.getData();
                if (appliesToSome(set)) {
                    b.setForeground(uiFactory.getDisabledTextColor());
                } else {
                    b.setForeground(uiFactory.getBlackColor());
                }
                b.setSelection(attrs.getPhotoSets().contains(set));
            }

        }
        this.thumbnail = attrs.getThumbnail();
        File f = new File(attrs.getImagePath());
        lblFilename.setText(f.getName());
        lblFilesize.setText(ConversionUtils.bytesToHumanReadable(f.length()));
        lblSize.setText(attrs.getImageData().width + "x" + attrs.getImageData().height);
    }

    /**
     * @param fields
     */
    private void addRotateButtons(Composite fields) {
        GridData data;
        FillLayout fill = new FillLayout();
        fill.spacing = 10;
        rotateButtons = new Composite(fields, SWT.NONE);
        rotateButtons.setLayout(fill);
        data = new GridData();
        data.horizontalSpan = 2;
        data.horizontalAlignment = SWT.CENTER;
        rotateButtons.setLayoutData(data);

        rotateLeft = new Button(rotateButtons, SWT.PUSH);
        rotateLeft.setImage(uiFactory.getRotateLeftImage());

        rotateRight = new Button(rotateButtons, SWT.PUSH);
        rotateRight.setImage(uiFactory.getRotateRightImage());

        SelectionAdapter rotateAdapter = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                ImageAttributesUI.this.rotateClicked(event);
            }
        };

        rotateLeft.addSelectionListener(rotateAdapter);
        rotateRight.addSelectionListener(rotateAdapter);
    }

    /**
     * @param fields
     */
    private void addPhotoSets(Composite fields) {
        OrganizeAbilities org = abilities.getOrganizeAbilities();
        if (org.hasPhotoSets()) {
            getLabel(fields, "Photo Sets", true);
            Collection myPhotoSets = attrs.getPhotoSets();
            Collection allPhotoSets = org.getPhotoSets();
            final ScrolledComposite photoSetList = new ScrolledComposite(fields, SWT.BORDER | SWT.V_SCROLL);
            photoSetList.setLayout(new FillLayout());
            buildPhotoSetButtons(myPhotoSets, allPhotoSets, photoSetList);
            getLabel(fields, ""); //$NON-NLS-1$
            Button b = new Button(fields, SWT.PUSH);
            b.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    PhotoSet set = ImageUploadApi.getInstance().newPhotoSet(attrs);
                    photoSetList.getContent().dispose();
                    buildPhotoSetButtons(attrs.getPhotoSets(), ImageUploadApi.getInstance().getAbilities().getOrganizeAbilities()
                            .getPhotoSets(), photoSetList);
                    addPhotosToPhotoset(set);
                }
            });
            photoSetList.addControlListener(new ControlAdapter() {
                @Override
                public void controlResized(ControlEvent e) {
                    ScrolledComposite s =(ScrolledComposite)e.widget;
                    s.getVerticalBar().setPageIncrement(s.getBounds().height);
                }
            });
            b.setText("New Photo Set");
            GridData data = new GridData();
            data.heightHint = 150;
            photoSetList.setLayoutData(data);
            photoSetList.getVerticalBar().setPageIncrement(data.heightHint);
        }

    }

    protected void addPhotosToPhotoset(PhotoSet set) {
        set.addPhoto(attrs);
    }

    protected void removePhotosFromPhotoset(PhotoSet set) {
        set.removePhoto(attrs);
    }

    protected boolean appliesToAll(PhotoSet p) {
        return attrs.getPhotoSets().contains(p);
    }

    protected boolean appliesToNone(PhotoSet p) {
        return !appliesToAll(p);
    }

    protected final boolean appliesToSome(PhotoSet p) {
        return !appliesToAll(p) && !appliesToNone(p);
    }

    /**
     * @param myPhotoSets
     * @param allPhotoSets
     * @param photoSetList
     * @return
     */
    private Composite buildPhotoSetButtons(Collection myPhotoSets, Collection allPhotoSets,
            final ScrolledComposite photoSetList) {
        Composite buttons = new Composite(photoSetList, SWT.NONE);
        photoSetList.setContent(buttons);
        photoSetButtons = new ArrayList<Button>(allPhotoSets.size());
        buttons.setLayout(new FillLayout(SWT.VERTICAL));
        for (Iterator iter = allPhotoSets.iterator(); iter.hasNext();) {
            PhotoSet set = (PhotoSet) iter.next();
            Button b = new Button(buttons, SWT.CHECK);
            if (appliesToSome(set)) {
                b.setForeground(uiFactory.getDisabledTextColor());
            }

            b.setBackground(uiFactory.getWhiteColor());
            b.setData(set);
            b.setText(set.getTitle());
            b.setSelection(myPhotoSets.contains(set));
            b.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    Button b = (Button) event.widget;
                    PhotoSet set = (PhotoSet) b.getData();
                    if (b.getSelection()) {
                        addPhotosToPhotoset(set);
                    } else {
                        removePhotosFromPhotoset(set);
                    }

                }

            });
            photoSetButtons.add(b);
        }
        buttons.setSize(buttons.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        return buttons;
    }

    private void addImageMetadata(Composite mainForm) {
        meta = new Composite(mainForm, SWT.NONE);
        meta.setLayout(new GridLayout(1, false));
        meta.setLayoutData(new GridData(GridData.FILL_BOTH));
        thumbnailComposite = new Composite(meta, SWT.NONE);
        GridData data = new GridData(GridData.FILL_BOTH);
        ImageData image = thumbnail.getImageData();
        int maxDimension = Math.max(image.width, image.height) + (2 * Thumbnail.BORDER_WIDTH);

        data.widthHint = maxDimension;
        data.heightHint = maxDimension;
        thumbnailComposite.setLayoutData(data);
        thumbnailComposite.setSize(maxDimension, maxDimension);
        addThumbnails(thumbnailComposite);
        thumbnailComposite.pack();
        thumbnailComposite.setToolTipText("Double Click to view full size");
        thumbnailComposite.addMouseListener(new MouseAdapter() {
            public void mouseDoubleClick(MouseEvent arg0) {
                if (!ImageAttributesUI.this.isPreviewing) {
                    ImageAttributesUI.this.isPreviewing = true;
                    new ImagePreview();
                }
            }
        });

        if (abilities.getRenderAbilities().isAbleToRotate()) {
            addRotateButtons(meta);
        }

        addFileInfo(meta);
        // addExif(meta);
    }

    private void addExif(Composite m) {
        Composite meta = new Composite(m, SWT.NONE);
        GridLayout layout = new GridLayout(1, true);

        meta.setLayout(layout);

        Label exif = getLabel(meta, Messages.getString("juploadr.ui.label.exifiptc"), true);
        exif.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        final Table t = new Table(meta, SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL);
        t.setLayoutData(new GridData(GridData.FILL_BOTH));
        t.setHeaderVisible(true);
        t.setLinesVisible(true);

        t.setLayoutData(new GridData(GridData.FILL_BOTH));
        String[] titles = { Messages.getString("juploadr.ui.label.property"),
                Messages.getString("juploadr.ui.label.value") };
        for (int i = 0; i < titles.length; i++) {
            TableColumn column = new TableColumn(t, SWT.NONE);
            column.setText(titles[i]);
        }

        final Map<String, Tag> tags = attrs.getPhotoMetadata();
        for (String key : tags.keySet()) {

            TableItem item = new TableItem(t, SWT.None);
            item.setText(0, key);
            Tag tag = tags.get(key);
            try {
                item.setText(1, tag.getDescription());
            } catch (MetadataException e) {
                // can't do much about this.
            }
        }
        for (int i = 0; i < titles.length; i++) {
            t.getColumn(i).pack();
        }
        t.setSize(t.computeSize(SWT.DEFAULT, 200));
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        data.heightHint = 200;
        data.widthHint = Math.min(350, meta.computeSize(SWT.DEFAULT, SWT.DEFAULT).x);
        meta.setLayoutData(data);

    }

    private void addFileInfo(Composite meta) {
        File f = new File(attrs.getImagePath());
        Composite comp = new Composite(meta, SWT.NONE);
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.horizontalSpacing = 5;
        comp.setLayout(gridLayout);
        comp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        Label title = getLabel(comp, Messages.getString("juploadr.ui.label.fileinfo"), true);
        title.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, true, true, 2, 1));
        getLabel(comp, Messages.getString("juploadr.ui.label.filename"));
        lblFilename = getLabel(comp, f.getName(), GridData.FILL_HORIZONTAL);

        getLabel(comp, Messages.getString("juploadr.ui.label.filesize"));
        lblFilesize = getLabel(comp, ConversionUtils.bytesToHumanReadable(f.length()), GridData.FILL_HORIZONTAL);
        getLabel(comp, Messages.getString("juploadr.ui.label.dimensions"));
        lblSize = getLabel(comp, attrs.getImageData().width + "x" + attrs.getImageData().height,
                GridData.FILL_HORIZONTAL);
    }

    protected void rotateClicked(SelectionEvent event) {
        if (event.widget == rotateLeft) {
            attrs.setRotation((attrs.getRotation() + 360 - 90) % 360);
            thumbnailComposite.redraw();
        } else {
            attrs.setRotation((attrs.getRotation() + 90) % 360);
            thumbnailComposite.redraw();
        }

    }

    protected void initDefaultValues(PhotoAbilities abilities) {
        if (abilities.hasPrivacy()) {
            radioPrivate.setSelection(!attrs.isPubliclyVisible());
            radioPublic.setSelection(attrs.isPubliclyVisible());

            if (attrs.isPubliclyVisible()) {
                if (abilities.hasFamilyPrivacy()) {
                    checkFamily.setEnabled(false);
                }
                if (abilities.hasFriendsPrivacy()) {
                    checkFriends.setEnabled(false);
                }
            }
        }
    }

    /**
     * @param thumbnailComposite
     */
    protected void addThumbnails(Composite thumbnailComposite) {
        Canvas tnCanvas = new Canvas(thumbnailComposite, SWT.NONE);
        PaintListener listener = new PaintListener() {
            public void paintControl(PaintEvent event) {
                ImageAttributesUI.this.paintControl(event);
            }
        };
        tnCanvas.addPaintListener(listener);
        thumbnailComposite.addPaintListener(listener);
    }

    private Composite getCheckBoxes(Composite parent, PhotoAbilities abilities) {
        Composite checks = null;
        if (abilities.hasFamilyPrivacy() || abilities.hasFriendsPrivacy()) {
            checks = new Composite(parent, SWT.NONE);
            GridLayout layout = new GridLayout();
            layout.numColumns = 1;
            layout.marginWidth = radioPublic.computeSize(SWT.DEFAULT, SWT.DEFAULT).x
                    + (radioPrivate.computeSize(SWT.DEFAULT, SWT.DEFAULT).x / 2) + 20;
            checks.setLayout(layout);
            GridData data = new GridData();
            data.horizontalSpan = 2;
            checks.setLayoutData(data);
            if (abilities.hasFriendsPrivacy()) {
                checkFriends = new Button(checks, SWT.CHECK);
                checkFriends.setText(Messages.getString("juploadr.ui.label.visibility.friend")); //$NON-NLS-1$
                checkFriends.setSelection(attrs.isFriendViewable());
            }
            if (abilities.hasFamilyPrivacy()) {
                checkFamily = new Button(checks, SWT.CHECK);
                checkFamily.setText(Messages.getString("juploadr.ui.label.visibility.family")); //$NON-NLS-1$
                checkFamily.setSelection(attrs.isFamilyViewable());
            }
        }
        return checks;
    }

    private Label getLabel(Composite parent, String text, boolean isHeading) {
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        Label lblTitle = new Label(parent, SWT.NONE);
        if (isHeading) {
            final Font f = new Font(ReusableUIFactory.getInstance().getDisplay(), new FontData("Sans", 10,
                    SWT.BOLD));
            lblTitle.setFont(f);
            parent.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent arg0) {
                    f.dispose();
                }
            });
        }
        lblTitle.setText(text);
        GC gc = new GC(lblTitle);

        // data.widthHint = gc.stringExtent(text).x;
        data.verticalAlignment = SWT.TOP;
        data.verticalIndent = 5;
        gc.dispose();
        // lblTitle.setLayoutData(data);
        return lblTitle;
    }

    private Label getLabel(Composite fields, String text) {
        return getLabel(fields, text, 0);
    }

    private Label getLabel(Composite fields, String text, int fillType) {

        GridData data = new GridData(fillType);

        Label lblTitle = new Label(fields, SWT.NONE);
        lblTitle.setText(text);
        GC gc = new GC(fields);

        data.widthHint = gc.stringExtent(text).x;
        data.verticalAlignment = SWT.TOP;
        data.verticalIndent = 5;
        gc.dispose();
        lblTitle.setLayoutData(data);
        return lblTitle;
    }

    private Text getText(Composite fields, String defaultValue) {
        return getText(fields, defaultValue, SWT.BORDER);
    }

    private Text getText(Composite fields, String defaultValue, int flags) {
        Text txt = new Text(fields, flags);
        if (defaultValue != null && defaultValue.trim().length() > 0) {
            txt.setText(defaultValue);
        }
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        if ((flags & SWT.MULTI) != 0) {
            data.heightHint = 60;
        }
        txt.setLayoutData(data);
        return txt;
    }

    /**
     * 
     */
    protected void gatherData() {
        PhotoAbilities photoAbilities = this.abilities.getPhotoAbilities();
        if (photoAbilities.hasTitle()) {
            attrs.setTitle(txtTitle.getText());
        }
        if (photoAbilities.hasDescription()) {
            attrs.setDescription(txtDescription.getText());
        }
        if (photoAbilities.hasTags()) {
            attrs.setTags(TagParser.parse(txtTags.getText()));
        }
        if (photoAbilities.hasPrivacy()) {
            if (radioPublic.getSelection()) {
                attrs.setPubliclyVisible(true);
            } else {
                attrs.setPubliclyVisible(false);
                if (photoAbilities.hasFamilyPrivacy()) {
                    attrs.setFamilyViewable(checkFamily.getSelection());
                }
                if (photoAbilities.hasFriendsPrivacy()) {
                    attrs.setFriendViewable(checkFriends.getSelection());
                }
            }
        }
        if (abilities.getGeoAbilities().hasGeoAbilities()) {
            attrs.setLocation(geoTab.getGeoLocation());
        }
    }

    /**
     * @return Returns the cancelled.
     */
    public boolean isCancelled() {
        return cancelled;
    }

    /**
     * @param cancelled
     *            The cancelled to set.
     */
    public void setCancelled(boolean cancelled) {
        this.cancelled = cancelled;
    }

    /**
     * 
     */
    private void saveAndExit() {
        save();
        window.dispose();
    }

    /**
     * 
     */
    private void save() {
        gatherData();
        tagHelper.updateTags(TagParser.parse(txtTags.getText()));
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
     */
    public void paintControl(PaintEvent event) {
        GC gfx = event.gc;

        Image photo = getPhoto(event.display, thumbnail);
        ImageData data = photo.getImageData();

        if (attrs.getRotation() != 0) {
            int rotation = attrs.getRotation();
            ImageData newData = Transforms.rotate(data, rotation);
            photo.dispose();
            photo = new Image(event.display, newData);
            data = photo.getImageData();
        }

        gfx.drawImage(photo, 0, 0);
        Color outline = new Color(event.display, 205, 205, 205);
        gfx.setForeground(outline);
        gfx.drawRectangle(0, 0, data.width - 1, data.height - 1);

        photo.dispose();
        outline.dispose();
    }

    private Image getPhoto(Display disp, Image thumb) {
        ImageData data = thumb.getImageData();
        Image photo = new Image(disp, data.width + 10, data.height + 10);
        GC gc = new GC(photo);
        gc.drawImage(thumb, 5, 5);
        gc.dispose();
        return photo;
    }

    private class EscListener implements Listener {
        public void handleEvent(Event event) {
            if (event.character == SWT.ESC) {
                window.dispose();
            }
        }
    }

    private class NoTabs implements TraverseListener {

        public void keyTraversed(TraverseEvent e) {
            if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
                e.doit = true;
            }
        }
    }

    public static void main(String[] args) {
        JUploadr uploadr = new JUploadr();
        File pearPath = new File("/home/steve/workspace/juploadr/test/resources/has_gps_and_iptc.jpg"); //$NON-NLS-1$
        ImageAttributes atts = new ImageAttributes(uploadr, uploadr.images, pearPath.getAbsolutePath());
        ImageAttributesUI ui = new ImageAttributesUI(atts);
        ui.show(ReusableUIFactory.getInstance().getDisplay());
    }

    public void widgetDefaultSelected(SelectionEvent e) {
        // do nothing
    }

    public void widgetSelected(SelectionEvent event) {
        PhotoAbilities photoAbilities = abilities.getPhotoAbilities();
        if (event.widget == btnOK) {
            saveAndExit();
        } else if (event.widget == btnCancel) {
            cancelled = true;
            attrs.setRotation(originalRotation);
            window.dispose();
        } else if (event.widget == radioPublic) {
            if (photoAbilities.hasFamilyPrivacy()) {
                checkFamily.setEnabled(false);
            }
            if (photoAbilities.hasFriendsPrivacy()) {
                checkFriends.setEnabled(false);
            }
        } else if (event.widget == radioPrivate) {
            if (photoAbilities.hasFamilyPrivacy()) {
                checkFamily.setEnabled(true);
            }
            if (photoAbilities.hasFriendsPrivacy()) {
                checkFriends.setEnabled(true);
            }
        } else if (event.widget == btnNext || event.widget == btnPrevious) {

            save();
            if (event.widget == btnNext) {
                setAttributes(this.attrs.getNext());
            } else {
                setAttributes(this.attrs.getPrevious());
            }
            geoTab.resetData(attrs);
            resetFieldData();
            thumbnailComposite.redraw();
            btnNext.setEnabled(attrs.hasNext());
            btnPrevious.setEnabled(attrs.hasPrevious());
        }
    }

    private class ImagePreview {
        public ImagePreview() {
            initUI();
        }

        private void initUI() {
            Display disp = ReusableUIFactory.getInstance().getDisplay();

            ImageData data = ImageAttributesUI.this.attrs.getImageData();
            Point screenSize = new Point(disp.getClientArea().width, disp.getClientArea().height);
            Point imageSize = new Point(data.width, data.height);

            Point smaller = new Point((int) Math.min(screenSize.x * 0.9, imageSize.x), (int) Math.min(
                    screenSize.y * 0.9, imageSize.y));

            final Shell s = new Shell(disp, SWT.BORDER | SWT.CLOSE);
            s.setLayout(new FillLayout());
            final Image img = new Image(disp, data);

            ScrolledComposite imgComposite = new ScrolledComposite(s, SWT.V_SCROLL | SWT.H_SCROLL);
            imgComposite.setLayout(new FillLayout());
            Canvas canvas = new Canvas(imgComposite, SWT.BORDER);

            canvas.addPaintListener(new PaintListener() {
                public void paintControl(PaintEvent event) {
                    event.gc.drawImage(img, 0, 0);
                }
            });
            canvas.setSize(imageSize);
            imgComposite.setContent(canvas);
            s.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent arg0) {
                    ImageAttributesUI.this.isPreviewing = false;
                    img.dispose();
                }
            });
            canvas.addMouseListener(new MouseAdapter() {
                public void mouseUp(MouseEvent event) {
                    s.dispose();
                }
            });
            s.setText(ImageAttributesUI.this.attrs.getTitle());

            s.setBounds((screenSize.x / 2) - smaller.x / 2, (screenSize.y / 2) - smaller.y / 2, smaller.x,
                    smaller.y);
            s.setVisible(true);
            disp.addFilter(SWT.KeyDown, new Listener() {
                public void handleEvent(Event e) {
                    if (e.keyCode == SWT.ESC) {
                        s.dispose();
                    }
                }
            });
        }
    }
}